/**
 * 
 */
package truerefactor.refactor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import truerefactor.graph.Accessibility;
import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;

/**
 * PushDownField - An implementation of the Push Down Field Refactoring
 * Technique.
 * 
 * @author Isaac Griffiht
 */
public class PushDownField extends Refactoring {

    /**
     * Creates a new PushDownField instance
     */
    public PushDownField()
    {
        this.name = "Push-down Field";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeNode [])
     */
    @Override
    public void execute(CodeGraph graph)
    {
        if (operands.isEmpty())
            return;

        AttributeNode field = (AttributeNode) operands.get(0);
        ClassNode parent = field.getParent();

        List<ClassNode> subClasses = parent.getSubClasses();
        if (subClasses.size() < 1)
            return; // can't perform pushdown field refactoring

        declareTheField(graph, field, subClasses);

        removeTheField(graph, field, subClasses);
    }

    private void declareTheField(CodeGraph graph, AttributeNode field, List<ClassNode> subClasses)
    {
        // 1. Declare the field in all subclasses
        for (int i = 0; i < subClasses.size(); i++)
        {
            AttributeNode newField = new AttributeNode(subClasses.get(i), field.getIdentifier(), field.getCode());
            newField.setAccessibility(Accessibility.Private);
            newField.setParent(subClasses.get(i));
            subClasses.get(i).addAttribute(newField);
            if (!field.isPrimitive())
                newField.setAssociatedClass(field.getAssociatedClass());

            graph.insert(newField);
            graph.insert(new Edge(subClasses.get(i), newField, EdgeType.Attribute));
            if (!newField.isPrimitive())
            {
                ClassNode type = newField.getAssociatedClass();
                graph.insert(new Edge(subClasses.get(i), type, EdgeType.Association));
            }
            
            for (CodeNode node : subClasses.get(i).getMethods()) {
                if (node instanceof MethodNode) 
                {
                    MethodNode method = (MethodNode) node;
                    if (method.getOtherAttributes().contains(field)) {
                        method.removeOtherAttribute(field);
                        method.addAssociatedAttribute(newField);
                        newField.addAssociatedClassMethod(method);
                        
                        if (graph.isEdge(field.getParent(), subClasses.get(i), EdgeType.Association)) {
                            graph.remove(graph.getEdge(field.getParent(), subClasses.get(i), EdgeType.Association));
                        }
                    }
                }
            }
            
            if (newField.getAssociatedMethods().isEmpty()) {
                subClasses.get(i).removeAttribute(newField);
                graph.remove(graph.getEdge(subClasses.get(i), newField, EdgeType.Attribute));
                graph.remove(newField);
                if (graph.isEdge(subClasses.get(i), newField.getAssociatedClass(), EdgeType.Association))
                    graph.remove(graph.getEdge(subClasses.get(i), newField.getAssociatedClass(), EdgeType.Association));
            }
        }
    }

    private void removeTheField(CodeGraph graph, AttributeNode field, List<ClassNode> subClasses)
    {
        // 2. Remove the field from the superclass
        // 3. Remove the field from all subclasses that don't need it
        List<MethodNode> otherMethods = field.getOtherMethods();
        List<ClassNode> otherClasses = new ArrayList<ClassNode>();
        for (MethodNode mNode : otherMethods)
        {
            ClassNode temp = mNode.getParentClass();
            if (subClasses.contains(temp))
            {
                otherClasses.add(temp);
            }
        }

        List<CodeNode> remList = new ArrayList<CodeNode>();
        remList.addAll(subClasses);

        remList.removeAll(otherClasses);

        for (CodeNode node : remList)
        {
            if (node instanceof ClassNode)
            {
                ClassNode temp = (ClassNode) node;

                if (!graph.isEdge(field, temp, EdgeType.Attribute) || !graph.isEdge(temp, field, EdgeType.Attribute))
                {
                    AttributeNode attribute = temp.getAttribute(field.getIdentifier());
                    if (attribute != null)
                    {
                        if (attribute.getType().equals(field.getType()))
                            graph.remove(attribute);
                    }
                }
            }
        }

        graph.remove(field);
    }
}
